#include "raknetclient.h"

RakNetClient::RakNetClient()
{
	m_connection_status = NO_CONNECT;
	m_shutdown = false;
	m_threads_to_shutdown = 0;
}

RakNetClient::~RakNetClient()
{
	// TODO: Is this necassary or even allowed?
	delete m_peer;
}

void RakNetClient::DoReceive()
{
	std::thread receive_thread([this] ()
	{
		RakNet::Packet *current_packet;

		// Always listen for packets.
		while (!m_shutdown)
		{
			// Sleep to prevent 100% CPU usage.
			std::this_thread::sleep_for(std::chrono::microseconds(1));

			// If there are new packets, filter them.
			for (current_packet = m_peer->Receive(); current_packet;
				m_peer->DeallocatePacket(current_packet), current_packet = m_peer->Receive())
			{
				Job new_job;
				new_job.data = new RakNet::BitStream(current_packet->data, current_packet->length, true);
				new_job.receive_guid = m_peer->GetMyGUID().ToString();
				new_job.sender_guid = current_packet->guid.ToString();
				m_work_manager.JobToLocal(new_job);
			}
		}

		// Thread ended.
		m_shutdown_mutex.lock();
		m_threads_to_shutdown--;
		m_shutdown_mutex.unlock();
	});

	m_shutdown_mutex.lock();
	m_threads_to_shutdown++;
	m_shutdown_mutex.unlock();

	// Leave receive thread running (asynchronous receive).
	receive_thread.detach();
}

void RakNetClient::DoSend()
{
	std::thread send_thread([this] ()
	{
		Job current_job;

		// Always check if there are jobs to be done.
		while (!m_shutdown)
		{
			// Prevent 100% CPU usage.
			std::this_thread::sleep_for(std::chrono::microseconds(1));

			while (m_work_manager.GetJobToRemote(current_job))
			{
				// If there was a specific receiver set, use it. It not, send it to our server (default case).
				RakNet::RakNetGUID receiver;
				if (current_job.receive_guid == "")
					receiver = m_server_guid;
				else
					receiver.FromString(current_job.receive_guid.c_str());

				// Send packet, never broadcast (because you are a client!).
				m_peer->Send(current_job.data, current_job.priority, current_job.reliability, 0, receiver, false);

				current_job.JobDone();
			}
		}

		// Thread ended.
		m_shutdown_mutex.lock();
		m_threads_to_shutdown--;
		m_shutdown_mutex.unlock();
	});

	m_shutdown_mutex.lock();
	m_threads_to_shutdown++;
	m_shutdown_mutex.unlock();

	send_thread.detach();
}

void RakNetClient::Connect(const char* host, unsigned short port)
{
	m_connection_status = PENDING;

	// TODO: Allow different kinds of socket? IPv4 and IPv6?
	RakNet::SocketDescriptor socket_descriptor;

	// Startup as peer.
	m_peer = RakNet::RakPeerInterface::GetInstance();
	m_peer->Startup(1, &socket_descriptor, 1);

	// Try to connect.
	RakNet::ConnectionAttemptResult result = m_peer->Connect(host, port, 0, 0);
	if (result != RakNet::CONNECTION_ATTEMPT_STARTED )
	{
		m_connection_status = NO_CONNECT;
		return;
	}

	// Wait until the connection has been established.
	std::thread connect_thread([this] ()
	{
		RakNet::Packet *current_packet;
		bool connect_done = false;

		while (!connect_done && !m_shutdown)
		{
			// Receive all packets.
			for (current_packet = m_peer->Receive(); current_packet && !connect_done;
				m_peer->DeallocatePacket(current_packet), current_packet = m_peer->Receive())
			{
				// If it says that the connection was established, set this info and start to send and receive.
				// If any other message comes in, the connection has failed.
				// TODO: More diverse message observation?
				switch (current_packet->data[0])
				{
				case ID_CONNECTION_REQUEST_ACCEPTED:
					m_connection_status = CONNECTED;
					m_server_guid = current_packet->guid;
					DoReceive();
					DoSend();
					connect_done = true;

					break;

				default:
					m_connection_status = NO_CONNECT;
					connect_done = true;
				};
			}
		}

		// Thread ended.
		m_shutdown_mutex.lock();
		m_threads_to_shutdown--;
		m_shutdown_mutex.unlock();
	});

	m_shutdown_mutex.lock();
	m_threads_to_shutdown++;
	m_shutdown_mutex.unlock();

	// Keep the connect thread running (allow asynchronous connect).
	connect_thread.detach();
}

void RakNetClient::Shutdown()
{
	m_shutdown = true;

	// Wait until our threads have shutdown.
	while (true)
	{
		m_shutdown_mutex.lock();
		if (m_threads_to_shutdown <= 0)
		{
			m_shutdown_mutex.unlock();
			break;
		}
		m_shutdown_mutex.unlock();

		std::this_thread::sleep_for(std::chrono::nanoseconds(1));
	}

	RakNet::RakPeerInterface::DestroyInstance(m_peer);
}